package com.lfy.service.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.lfy.cache.FileShareCache;
import com.lfy.modules.common.config.sys.DictionariesKey;
import com.lfy.modules.common.config.sys.SysDictionaries;
import com.lfy.domain.dto.ShareFileDTO;
import com.lfy.domain.dto.WebShareCheckDTO;
import com.lfy.domain.vo.WebFileVO;
import com.lfy.domain.vo.WebShareFileDetailVO;
import com.lfy.domain.vo.WebShareFileVO;
import com.lfy.encrypt.AESUtil;
import com.lfy.modules.blog.domain.HomeDetail;
import com.lfy.modules.blog.service.HomeDetailService;
import com.lfy.modules.blog.service.UserService;
import com.lfy.modules.common.custom.BatchDownFIleParams;
import com.lfy.modules.common.custom.FileService;
import com.lfy.modules.common.custom.WebShareDownloadVO;
import com.lfy.modules.common.custom.constants.*;
import com.lfy.modules.common.domain.BaseEnum;
import com.lfy.modules.common.domain.R;
import com.lfy.modules.common.domain.entity.UserEntity;
import com.lfy.modules.common.utils.RequestUtil;
import com.lfy.modules.disk.domain.DiskFileEntity;
import com.lfy.modules.disk.domain.DiskShareDetailEntity;
import com.lfy.modules.disk.service.DiskFileService;
import com.lfy.modules.disk.service.DiskShareDetailService;
import com.lfy.modules.letter.domain.SiteLetterEntity;
import com.lfy.modules.letter.domain.builder.Letter;
import com.lfy.modules.letter.domain.builder.LetterEnum;
import com.lfy.modules.letter.domain.event.LetterEvent;
import com.lfy.service.WebDiskService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Collectors;

/**
 * 博客首页网盘业务实现类（首页网盘功能、文件分享、文件下载、文件查看）
 *
 * @author <a href="https://www.lvfeiy.top">lvfeiyang</a>
 * @date 2024/6/20 11:24
 */
@Service
@Slf4j
public class WebDiskServiceImpl implements WebDiskService {
    @Resource
    private DiskFileService diskFileService;
    @Resource
    private UserService userService;
    @Resource
    private HomeDetailService homeDetailService;
    @Resource
    private FileService fileService;
    @Value("${ase.key}")
    private String aseKey;
    @Resource
    private DiskShareDetailService diskShareDetailService;
    @Resource
    private ThreadPoolExecutor mainThreadPool;

    @Override
    public R<List<WebFileVO>> getFileList(String url) {
        UserEntity user = this.userService.getOne(new LambdaQueryWrapper<UserEntity>().eq(UserEntity::getUrl, url));
        if (user == null) {
            return R.error("用户不存在");
        }
        List<DiskFileEntity> list = diskFileService.list(new LambdaQueryWrapper<DiskFileEntity>().eq(DiskFileEntity::getTenantId, user.getId()).eq(DiskFileEntity::getIsPrivate, PrivateEnum.PUBLIC.getCode()));
        if (CollectionUtils.isEmpty(list)) {
            return R.success(new ArrayList<>());
        }
        String prefix = SysDictionaries.getValue(DictionariesKey.SYS_MINIO_SITE);
        List<WebFileVO> result = list.stream().filter(item -> item.getParentId().equals(0L)).map(item -> {
            WebFileVO webFileVO = new WebFileVO();
            BeanUtils.copyProperties(item, webFileVO);
            webFileVO.setTypeName(FileTypeEnum.getMsg(item.getType()));
            webFileVO.setCreateTime(DateUtil.formatDateTime(item.getCreateTime()));
            webFileVO.setChildren(getChilren(item.getId(), list, prefix));
            if (StringUtils.isNotBlank(item.getFilePath())) {
                webFileVO.setFilePath(prefix + item.getFilePath());
            }
            return webFileVO;
        }).collect(Collectors.toList());

        return R.success(result);
    }

    @Override
    public void download(String url, Long id, HttpServletResponse response) {
        UserEntity user = this.userService.getOne(new LambdaQueryWrapper<UserEntity>().eq(UserEntity::getUrl, url));
        if (user == null) {
            throw new IllegalArgumentException("用户不存在");
        }

        DiskFileEntity diskFileEntity = diskFileService.getById(id);
        if (diskFileEntity == null) {
            throw new IllegalArgumentException("文件不存在");
        } else if (!diskFileEntity.getTenantId().equals(user.getId())) {
            throw new IllegalArgumentException("文件不属于该用户");
        } else if (diskFileEntity.getIsPrivate().equals(PrivateEnum.PRIVATE.getCode())) {
            throw new IllegalArgumentException("私有文件无法下载");
        } else if (diskFileEntity.getType().equals(FileTypeEnum.FOLDER.getCode())) {
            throw new IllegalArgumentException("文件夹无法下载");
        }

        this.fileService.downloadToStream(diskFileEntity.getName(), diskFileEntity.getFilePath(),diskFileEntity.getType(), response);
    }

    @Override
    public R<WebShareFileVO> getShareFile(String shareKey, String shareCode) {
        byte[] decode = Base64.getUrlDecoder().decode(shareKey);
        String key = new String(decode, StandardCharsets.UTF_8);
        try {
            String shareId = AESUtil.decryptString(key, aseKey);
            if (StringUtils.isBlank(shareId)) {
                return R.error("分享码不存在");
            }
            DiskShareDetailEntity fileDetail = this.diskShareDetailService.getById(Long.parseLong(shareId));
            if (Objects.isNull(fileDetail)) {
                return R.error("分享码不存在");
            }
            if (!fileDetail.getShareTimeType().equals(FileShareOutTimeEnum.PERMANENT.getCode())) {
                //不是永久分享
                Long timeStampSave = fileDetail.getTimeStampSave();
                Long currentTimeMillis = System.currentTimeMillis();
                Long outTime = FileShareOutTimeEnum.getOutTime(fileDetail.getShareTimeType());
                if (currentTimeMillis - timeStampSave > outTime) {
                    //分享码已过期
                    return R.error("分享码已过期");

                }
            }
            if (fileDetail.getShareLimitType().equals(FileShareLimitType.PER_USER.getCode())) {
                synchronized (LockPrefixConstant.SHARE_LOCK_PREFIX.concat(fileDetail.getId().toString()).intern()) {
                    if (fileDetail.getShareLimitSurplus() <= 0) {
                        //分享次数已用完
                        return R.error("分享次数已用完");
                    }
                }

            }
            Long tenantId = fileDetail.getTenantId();
            HomeDetail homeDetail = this.homeDetailService.getOne(Wrappers.lambdaQuery(HomeDetail.class).eq(HomeDetail::getTenantId, tenantId));
            if (Objects.isNull(homeDetail)) {
                return R.error("用户不存在");
            }
            Long fileId = fileDetail.getFileId();
            DiskFileEntity file = this.diskFileService.getById(fileId);
            if (Objects.isNull(file)) {
                return R.error("文件不存在");
            }
            WebShareFileVO webShareFileVO = new WebShareFileVO();
            webShareFileVO.setFileName(file.getName());
//            webShareFileVO.setShareCode(fileDetail.getShareCode().toLowerCase());
            webShareFileVO.setAuthorName(homeDetail.getAuthorName());
            String prefix = SysDictionaries.getValue(DictionariesKey.SYS_MINIO_SITE);
            webShareFileVO.setAuthorHeadImg(prefix + homeDetail.getAuthorHeaderImg());
            webShareFileVO.setAuthorSignature(homeDetail.getAuthorSign());
            webShareFileVO.setFileId(file.getId());
//            String token = setToken(file);
//            webShareFileVO.setToken(token);
            return R.success(webShareFileVO);

        } catch (Exception e) {
            log.error("分享码解析失败", e);
            return R.error("分享码解析失败");
        }
    }

    @Override
    public R<String> checkShareCode(WebShareCheckDTO dto) {
        String shareKey = dto.getShareKey();
        String shareCode = dto.getShareCode();
        byte[] decode = Base64.getUrlDecoder().decode(shareKey);
        String key = new String(decode, StandardCharsets.UTF_8);
        try {
            String shareId = AESUtil.decryptString(key, aseKey);
            if (StringUtils.isBlank(shareId)) {
                return R.error("分享码不存在");
            }
            DiskShareDetailEntity fileDetail = this.diskShareDetailService.getById(Long.parseLong(shareId));
            if (Objects.isNull(fileDetail)) {
                return R.error("分享码不存在");
            }
            String code = fileDetail.getShareCode().toLowerCase();

            if (!code.equals(shareCode.toLowerCase())) {
                return R.error("分享码错误");
            }
            DiskFileEntity file = this.diskFileService.getById(fileDetail.getFileId());

            if (Objects.isNull(file)) {
                return R.error("文件不存在");
            }
            String token = setToken(file);
            String ip = RequestUtil.getIp();
            mainThreadPool.execute(() -> {
                //更新分享次数
                synchronized (LockPrefixConstant.SHARE_LOCK_PREFIX.concat(file.getId().toString()).intern()) {
                    if (fileDetail.getShareLimitType().equals(FileShareLimitType.PER_USER.getCode())) {
                        fileDetail.setShareLimitSurplus(fileDetail.getShareLimitSurplus() - 1);
                        this.diskShareDetailService.updateById(fileDetail);
                    }
                }
                //发送系统消息
                SiteLetterEntity letter = Letter.builder(LetterEnum.LetterType.SYS_SEND.getCode(), LetterEnum.SysSubjectType.DISK_EVENT.getSubject(ip), Letter.builderShareFIleContent(file.getName(), ip),
                        fileDetail.getTenantId(), LetterEnum.SendEmailType.NO.getCode());
                SpringUtil.publishEvent(new LetterEvent(this, letter));

            });
            return R.success(token);
        } catch (Exception e) {
            log.error("分享码解析失败", e);
            return R.error("分享码解析失败");
        }

    }

    @Override
    public R<List<WebShareFileDetailVO>> getShareFileList(ShareFileDTO dto) {
        if (Objects.nonNull(dto.getParentId())) {
            List<DiskFileEntity> list = this.diskFileService.list(Wrappers.lambdaQuery(DiskFileEntity.class).eq(DiskFileEntity::getParentId, dto.getParentId()));
            if (CollectionUtils.isEmpty(list)) {
                return R.success(new ArrayList<>());
            }
            List<WebShareFileDetailVO> result = list.stream().map(item -> {
                WebShareFileDetailVO vo = new WebShareFileDetailVO();
                vo.setFileSize(item.getFileSize());
                vo.setFileUrl(item.getFilePath());
                vo.setFileName(item.getName());
                vo.setId(item.getId());
                vo.setType(item.getType());
                vo.setTypeName(FileTypeEnum.getMsg(item.getType()));
                vo.setCreateTime(DateUtil.formatDateTime(item.getCreateTime()));
                return vo;
            }).collect(Collectors.toList());
            return R.success(result);
        }
        DiskFileEntity file = this.diskFileService.getOne(Wrappers.lambdaQuery(DiskFileEntity.class).eq(DiskFileEntity::getId, dto.getFileId()));

        if (Objects.isNull(file)) {
            return R.error("文件不存在");
        }
        ArrayList<WebShareFileDetailVO> result = new ArrayList<>();
        WebShareFileDetailVO webShareFileDetailVO = new WebShareFileDetailVO();
        webShareFileDetailVO.setFileSize(file.getFileSize());
        webShareFileDetailVO.setFileUrl(file.getFilePath());
        webShareFileDetailVO.setFileName(file.getName());
        webShareFileDetailVO.setId(file.getId());
        webShareFileDetailVO.setType(file.getType());
        webShareFileDetailVO.setTypeName(FileTypeEnum.getMsg(file.getType()));
        webShareFileDetailVO.setCreateTime(DateUtil.formatDateTime(file.getCreateTime()));
        result.add(webShareFileDetailVO);
        return R.success(result);

    }

    @Override
    public R<List<WebShareDownloadVO>> batchDownload(List<Long> ids, String token) {
        if (StringUtils.isBlank(token) || CollectionUtils.isEmpty(ids)) {
            return R.error("参数校验错误");
        }
        Set<Long> set = FileShareCache.get(token);
        if (CollectionUtils.isEmpty(set)) {
            return R.error("身份已过期，请重新提取文件");
        }
        if (!set.containsAll(ids)) {
            return R.error("禁止越权下载");
        }

        R<List<DiskFileEntity>> result = getNoFolderFiles(ids);
        if(!BaseEnum.commonEnum.SUCCESS.getCode().equals(result.getCode())){
            return R.error(result.getMsg());
        }
        List<DiskFileEntity> noFolder = result.getData();
        if(CollectionUtils.isEmpty(noFolder)){
            return R.error("没有可下载的文件");
        }

        List<BatchDownFIleParams> params = noFolder.stream().map(item -> {
            BatchDownFIleParams batchDownFIleParams = new BatchDownFIleParams();
            batchDownFIleParams.setFilePath(item.getFilePath());
            batchDownFIleParams.setFileName(item.getName());
            return batchDownFIleParams;
        }).collect(Collectors.toList());
        List<WebShareDownloadVO> responseEntities = fileService.batchDownload(params);
        return R.success(responseEntities);
    }

    @Override
    public void downloadToStream(Long id, String token, HttpServletResponse response) {

        Set<Long> set = FileShareCache.get(token);
        if (CollectionUtils.isEmpty(set)) {
            return ;
        }
        if (!set.contains(id)) {
            return;
        }
        DiskFileEntity file = this.diskFileService.getById(id);
        if (Objects.isNull(file)) {

            return;
        }
        if(FileTypeEnum.FOLDER.getCode().equals(file.getType())){
            return;
        }
        fileService.downloadToStream(file.getName(), file.getFilePath(), file.getType(),response);
    }

    @Override
    public R<List<Long>> getNoFolderFilesId(String token, List<Long> ids) {
        Set<Long> set = FileShareCache.get(token);
        if (CollectionUtils.isEmpty(set)) {
            return R.error("身份已过期，请重新提取文件");
        }
        if (!set.containsAll(ids)) {
            return R.error("禁止越权下载");
        }
        R<List<DiskFileEntity>> r = getNoFolderFiles(ids);
        if (!BaseEnum.commonEnum.SUCCESS.getCode().equals(r.getCode())) {
            return R.error(r.getMsg());
        }
        List<DiskFileEntity> noFolder = r.getData();
        if (CollectionUtils.isEmpty(noFolder)) {
            return R.error("没有可下载的文件");
        }
        List<Long> result = noFolder.stream().map(DiskFileEntity::getId).distinct().collect(Collectors.toList());
        return R.success(result);
    }

    /**
     * 根据id集合获取 所有 非文件夹文件子节点列表
     * @param ids 文件id列表
     * @return R<List<DiskFileEntity>>
     */
    private R<List<DiskFileEntity>> getNoFolderFiles(List<Long> ids) {
        List<DiskFileEntity> list = diskFileService.list(Wrappers.lambdaQuery(DiskFileEntity.class).in(DiskFileEntity::getId, ids)
                .select(DiskFileEntity::getName, DiskFileEntity::getFilePath, DiskFileEntity::getId, DiskFileEntity::getType));
        if (CollectionUtils.isEmpty(list)) {
            return R.error("文件不存在");
        }
        List<Long> folderId = list.stream().filter(item -> item.getType().equals(FileTypeEnum.FOLDER.getCode())).map(DiskFileEntity::getId).collect(Collectors.toList());
        List<DiskFileEntity> noFolder = list.stream().filter(item -> !item.getType().equals(FileTypeEnum.FOLDER.getCode())).collect(Collectors.toList());
        if (!CollectionUtils.isEmpty(folderId)) {
            List<DiskFileEntity> allFile = getAllFile(folderId);
            noFolder.addAll(allFile);
        }
        if (CollectionUtils.isEmpty(noFolder)) {
            return R.error("文件不存在");
        }
        return R.success(noFolder);
    }

    /**
     * 递归获取所有子节点
     * @param ids 文件夹id列表
     * @return List<DiskFileEntity>
     */
    private List<DiskFileEntity> getAllFile(List<Long> ids) {
        List<DiskFileEntity> list = this.diskFileService.list(Wrappers.lambdaQuery(DiskFileEntity.class).in(DiskFileEntity::getParentId, ids)
                .select(DiskFileEntity::getName, DiskFileEntity::getFilePath, DiskFileEntity::getId, DiskFileEntity::getType));
        if (CollectionUtils.isEmpty(list)) {
            return new ArrayList<>();
        }
        List<Long> folderId = list.stream().filter(item -> item.getType().equals(FileTypeEnum.FOLDER.getCode())).map(DiskFileEntity::getId).collect(Collectors.toList());
        List<DiskFileEntity> noFolder = list.stream().filter(item -> !item.getType().equals(FileTypeEnum.FOLDER.getCode())).collect(Collectors.toList());

        if (!CollectionUtils.isEmpty(folderId)) {
            List<DiskFileEntity> allFile = getAllFile(folderId);
            noFolder.addAll(allFile);
        }
        return noFolder;
    }

    /**
     * 设置提取文件令牌
     * @param file 文件实体
     * @return token令牌
     */
    private String setToken(DiskFileEntity file) {
        String token = UUID.randomUUID().toString().replace("-", "");

        Set<Long> set = new HashSet<>();
        if (!file.getType().equals(FileTypeEnum.FOLDER.getCode())) {
            //不是文件夹
            set.add(file.getId());
            FileShareCache.put(token, set);
            return token;
        }
        //是文件夹，递归
        Set<Long> allFileId = findAllFileId(file);
        log.info("文件夹下所有文件id:{}", allFileId);
        FileShareCache.put(token, allFileId);
        return token;
    }

    /**
     * 递归获取所有子节点id
     * @param file 文件实体
     * @return Set<Long>
     */
    private Set<Long> findAllFileId(DiskFileEntity file) {
        Set<Long> set = new HashSet<>();
        set.add(file.getId());
        List<DiskFileEntity> list = diskFileService.list(Wrappers.lambdaQuery(DiskFileEntity.class)
                .eq(DiskFileEntity::getParentId, file.getId())
                .select(DiskFileEntity::getId, DiskFileEntity::getParentId, DiskFileEntity::getType));
        if (CollectionUtils.isEmpty(list)) {
            return set;
        }
        CompletableFuture.allOf(list.stream().map(item -> CompletableFuture.runAsync(() -> {
            if (item.getType().equals(FileTypeEnum.FOLDER.getCode())) {
                set.addAll(findAllFileId(item));
            } else {
                set.add(item.getId());
            }
        }, mainThreadPool)).toArray(CompletableFuture[]::new)).join();
        return set;
    }

    private List<WebFileVO> getChilren(Long id, List<DiskFileEntity> list, String prefix) {

        return list.stream().filter(item -> item.getParentId().equals(id)).map(item -> {
            WebFileVO webFileVO = new WebFileVO();
            BeanUtils.copyProperties(item, webFileVO);
            webFileVO.setTypeName(FileTypeEnum.getMsg(item.getType()));
            webFileVO.setCreateTime(DateUtil.formatDateTime(item.getCreateTime()));
            if (StringUtils.isNotBlank(item.getFilePath())) {
                webFileVO.setFilePath(prefix + item.getFilePath());
            }
            webFileVO.setChildren(getChilren(item.getId(), list, prefix));
            return webFileVO;
        }).collect(Collectors.toList());

    }
}
